﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace DiscreteMath.Tests
{
    public class AutomataFactory : TestFactory<NondeterministicAutomaton>
    {
        private char a = 'a',
            b = 'b',
            c = 'c',
            d = 'd',
            e = 'e',
            f = 'f',
            g = 'g',
            h = 'h',
            j = 'j',
            k = 'k',
            n = 'n',
            p = 'p',
            q = 'q';

        private TestableAutomaton
            endsWithB,
            oddNumberOfBs,
            ab,
            aUnionB,
            containsEitherBBOrBab;

        private static AutomataFactory factory = new AutomataFactory();
        private ISet<char> alphabet = new HashSet<char> { 'a', 'b' };

        private AutomataFactory()
        { 
        }

        /// <summary>
        /// Gets the instance of the <see cref="AutomataFactory"/>.
        /// </summary>
        public static AutomataFactory Factory
        {
            get
            {
                return factory;
            }
        }

        public ISet<char> Alphabet
        {
            get
            {
                return this.alphabet;
            }
        }

        /// <summary>
        /// Gets an automaton that recognizes L = { w | w = ub, u is a random word }
        /// </summary>
        public TestableAutomaton EndsWithB
        {
            get
            {
                if (this.endsWithB == null)
                {
                    var states = new HashSet<State>() { p, q };
                    var starting = p;
                    var final = new HashSet<State>() { q };
                    var transitions = new Dictionary<Configuration, State>()
			        {
				        { new Configuration(p, 'a'), p },
				        { new Configuration(p, 'b'), q },
				        { new Configuration(q, 'a'), p },
				        { new Configuration(q, 'b'), q },
			        };

                    var automaton = new DeterministicAutomaton(states, Alphabet, transitions, starting, final);
                    Func<string, bool> recognitionFunc = (word) => word.EndsWith("b");
                    int minimized = 2;
                    int determined = 2;
                    string regex = "(a" + SpecialSigns.UnionSign + "b)*b";

                    this.endsWithB = 
                        new TestableAutomaton(
                            automaton, 
                            "L = { w | w = ub, u is a random word }", 
                            recognitionFunc, 
                            regex,
                            minimized, 
                            determined);
                }
                return this.endsWithB;
            }
        }

        /// <summary>
        /// Gets an automaton that recognizes L = { w | w has odd number of 'b's }.
        /// </summary>
        public TestableAutomaton OddNumberOfBs
        {
            get
            {
                if (this.oddNumberOfBs == null)
                {
                    //Recognizes words that have odd number of 1's
                    var states = new HashSet<State> { a, b };
                    var starting = a;
                    var final = new HashSet<State> { b };
                    var transitions = new Dictionary<Configuration, State>()
		            {
			            { new Configuration(a, 'a'), a },
			            { new Configuration(a, 'b'), b },
			            { new Configuration(b, 'a'), b },
			            { new Configuration(b, 'b'), a },
		            };

                    var automaton = new DeterministicAutomaton(states, Alphabet, transitions, starting, final);
                    Func<string, bool> recognitionFunc = (word) => word.Count(x => x == 'b') % 2 == 1;
                    int minimized = 2;
                    int determined = 2;
                    string regex = "a*b(a*ba*ba*" + SpecialSigns.UnionSign + "a)*";

                    this.oddNumberOfBs = 
                        new TestableAutomaton(
                            automaton, 
                            "L = { w | w has odd number of 'b's }",
                            recognitionFunc,
                            regex,
                            minimized,
                            determined);
                }
                return this.oddNumberOfBs;
            }
        }

        /// <summary>
        /// Gets an automaton that recognizes L = { "ab" }
        /// </summary>
        public TestableAutomaton AB
        {
            get
            {
                if (this.ab == null)
                {
                    var states = new HashSet<State> { c, d, e, n };
                    var starting = c;
                    var final = new HashSet<State> { e };
                    var transitions = new TransitionDictionary()
			        {
				        { new Configuration(c, 'a'), new HashSet<State> { d } },
				        { new Configuration(c, 'b'), new HashSet<State> { n } },
				        { new Configuration(d, 'a'), new HashSet<State>() { n } },
				        { new Configuration(d, 'b'), new HashSet<State>() { e } },
				        { new Configuration(e, 'a'), new HashSet<State>() { n } },
				        { new Configuration(e, 'b'), new HashSet<State>() { n } },
				        { new Configuration(n, 'b'), new HashSet<State>() { n } },
				        { new Configuration(n, 'a'), new HashSet<State>() { n } },
			        };

                    var automaton = new NondeterministicAutomaton(states, Alphabet, transitions, starting, final);
                    Func<string, bool> recognitionFunc = (word) => word == "ab";
                    int minimized = 4;
                    int determined = 4;
                    string regex = "ab";

                    this.ab = 
                        new TestableAutomaton(
                            automaton,
                            "L = { 'ab' }",
                            recognitionFunc,
                            regex,
                            minimized,
                            determined);
                }
                return this.ab;
            }
        }

        /// <summary>
        /// Gets an automaton that recognizes L = { w | w = aa⋃bb }
        /// </summary>
        public TestableAutomaton AAunionBB
        {
            get
            {
                if (this.aUnionB == null)
                {
                    var states = new HashSet<State>() { a, b, c, d };
                    var final = new HashSet<State>() { d };
                    State starting = a;
                    var transitions = new Dictionary<Configuration, State>()
                    {
                        { new Configuration(a, 'a'), b },
                        { new Configuration(a, 'b'), c },
                        { new Configuration(b, 'a'), d },
                        { new Configuration(c, 'b'), d },
                    };
                    var alphabet = new HashSet<char>() { 'a', 'b' };
                    var automaton = new DeterministicAutomaton(states, alphabet, transitions, starting, final);
                    int minimized = 5;
                    int determined = 5;

                    this.aUnionB = 
                        new TestableAutomaton(
                            automaton: automaton, 
                            name: "L = { w | w = aa⋃bb }",
                            recognition: (word) => word == "bb" || word == "aa",
                            regex: "aa" + SpecialSigns.UnionSign + "bb",
                            minimizedNumberOfStates: minimized,
                            determinedNumberOfStates: determined);
                }

                return this.aUnionB;
            }
        }

        /// <summary>
        /// Gets an automaton that recognizes L = { w | w contains "bb" or "bab" }. Has an extra useless state.
        /// </summary>
        public TestableAutomaton ContainsEitherBBorBAB
        {
            get
            {
                if (containsEitherBBOrBab == null)
                {
                    var states = new HashSet<State>() { "q0", "q1", "q2", "q3", "q4" };
                    var final = new HashSet<State>() { "q4" };
                    var starting = "q0";
                    var transitions = new TransitionDictionary()
                    {
                        { "q0", a, new HashSet<State>() { "q0" } },
                        { "q0", b, new HashSet<State>() { "q0", "q1" } },
                        { "q1", a, new HashSet<State>() { "q3" } },
                        { "q1", b, new HashSet<State>() { "q4" } },
                        { "q3", b, new HashSet<State>() { "q4" } },
                        { "q4", a, new HashSet<State>() { "q4" } },
                        { "q4", b, new HashSet<State>() { "q4" } }
                    };

                    var automaton = new NondeterministicAutomaton(states, Alphabet, transitions, starting, final);
                    Func<string, bool> recognitionFunc = (word) => word.Contains("bb") || word.Contains("bab");
                    int minimized = 4;
                    int determined = 6;
                    string regex = "(a" + SpecialSigns.UnionSign + "b)*(bb" + SpecialSigns.UnionSign + "bab)(a" + SpecialSigns.UnionSign + "b)*";

                    this.containsEitherBBOrBab = 
                        new TestableAutomaton(
                            automaton, 
                            "L = { w | w contains 'bb' or 'bab' }", 
                            recognitionFunc,
                            regex,
                            minimized,
                            determined);
                }
                return containsEitherBBOrBab;
            }
        }
    }
}
